﻿#include "chattextviewer.h"

#include <QDateTime>
#include <QImageReader>
#include <QMenu>
#include <QScrollBar>
#include <QTextTable>
#include "characterset.h"
#include "emotionresource.h"
#include "httpget.h"
#include "imageviewer.h"
#include "webimageobject.h"

ChatTextViewer::ChatTextViewer(QWidget *parent) : QTextBrowser(parent), lastTimeStamp(0)
{
    setOpenLinks(false);
    setOpenExternalLinks(false);
    connect(this, &QTextBrowser::anchorClicked, this, &ChatTextViewer::whenAnchorClicked);
    auto doc = document();
    auto documentLayout = doc->documentLayout();
    //documentLayout->unregisterHandler(QTextFormat::ImageObject);
    auto handler = new WebImageObject(this);
    connect(handler, SIGNAL(requestDownload(QString)), SLOT(downloadRequested(QString)), Qt::QueuedConnection);
    documentLayout->registerHandler(WebImageObject::objectType, handler);

    QPixmap chatto_bg9(":/gui/chatto_bg_normal.9.png");
    chatto_bg.topLeft = chatto_bg9.copy(1,0,20,21);
    chatto_bg.topCenter = chatto_bg9.copy(36,0,1,21);
    chatto_bg.topRight = chatto_bg9.copy(63,0,33,21);
    chatto_bg.rightCenter = chatto_bg9.copy(63,48,33,1);
    chatto_bg.arrow = chatto_bg9.copy(63,21,33,27);
    chatto_bg.bottomRight = chatto_bg9.copy(63,50,33,21);
    chatto_bg.bottomCenter = chatto_bg9.copy(37,50,1,21);
    chatto_bg.bottomLeft = chatto_bg9.copy(1,50,20,21);
    chatto_bg.leftCenter = chatto_bg9.copy(1,48,20,1);
    chatto_bg.center = QColor(178, 232, 102);

    doc->addResource(QTextDocument::ImageResource, QUrl(QString::number(chatto_bg.topLeft.cacheKey())), chatto_bg.topLeft);
    doc->addResource(QTextDocument::ImageResource, QUrl(QString::number(chatto_bg.topRight.cacheKey())), chatto_bg.topRight);
    doc->addResource(QTextDocument::ImageResource, QUrl(QString::number(chatto_bg.arrow.cacheKey())), chatto_bg.arrow);
    doc->addResource(QTextDocument::ImageResource, QUrl(QString::number(chatto_bg.bottomLeft.cacheKey())), chatto_bg.bottomLeft);
    doc->addResource(QTextDocument::ImageResource, QUrl(QString::number(chatto_bg.bottomRight.cacheKey())), chatto_bg.bottomRight);

    QPixmap chatfrom_bg9(":/gui/chatfrom_bg_normal.9.png");
    chatfrom_bg.topLeft = chatfrom_bg9.copy(1,0,31,21);
    chatfrom_bg.topCenter = chatfrom_bg9.copy(55,0,1,21);
    chatfrom_bg.topRight = chatfrom_bg9.copy(76,0,20,21);
    chatfrom_bg.rightCenter = chatfrom_bg9.copy(76,48,20,1);
    chatfrom_bg.bottomRight = chatfrom_bg9.copy(76,50,20,21);
    chatfrom_bg.bottomCenter = chatfrom_bg9.copy(54,50,1,21);
    chatfrom_bg.bottomLeft = chatfrom_bg9.copy(1,50,31,21);
    chatfrom_bg.leftCenter = chatfrom_bg9.copy(1,48,31,1);
    chatfrom_bg.arrow = chatfrom_bg9.copy(1,21,31,27);
    chatfrom_bg.center = QColor(253, 253, 253);

    doc->addResource(QTextDocument::ImageResource, QUrl(QString::number(chatfrom_bg.topLeft.cacheKey())), chatfrom_bg.topLeft);
    doc->addResource(QTextDocument::ImageResource, QUrl(QString::number(chatfrom_bg.topRight.cacheKey())), chatfrom_bg.topRight);
    doc->addResource(QTextDocument::ImageResource, QUrl(QString::number(chatfrom_bg.arrow.cacheKey())), chatfrom_bg.arrow);
    doc->addResource(QTextDocument::ImageResource, QUrl(QString::number(chatfrom_bg.bottomLeft.cacheKey())), chatfrom_bg.bottomLeft);
    doc->addResource(QTextDocument::ImageResource, QUrl(QString::number(chatfrom_bg.bottomRight.cacheKey())), chatfrom_bg.bottomRight);
}

int ChatTextViewer::showChat(bool fromSelf, const QPixmap &icon, const QString &name, const QString &content, qint64 timestamp)
{
    QString newContent = content;
    if(!newContent.isEmpty())
    {
        QRegExp rx(QLatin1String("\\[([\\x4e00-\\x9fa5\\w]+)\\]"));
        rx.setMinimal(true);
        int pos = 0;
        auto emotions = EmotionResource::instance();
        while ((pos = rx.indexIn(newContent, pos)) != -1)
        {
            QString emotionName = rx.cap(1);
            int length = rx.matchedLength();
            if(emotions->containsName(emotionName))
            {
                if(!document()->resource(QTextDocument::ImageResource, emotionName).isValid())
                {
                    QPixmap pix = EmotionResource::instance()->findByName(emotionName);
                    if(!pix.isNull())
                        document()->addResource(QTextDocument::ImageResource, emotionName, pix);
                }
                const QString toReplace = QLatin1String("<img src=\"") + emotionName.toHtmlEscaped() + QLatin1String("\" />");
                newContent.replace(pos, rx.matchedLength(), toReplace);
                pos += toReplace.length();
            }
            else
                pos += length;
        }
    }
    auto cursor = appendTable(icon, name, timestamp, fromSelf ? (&ChatTextViewer::insertTo) : (&ChatTextViewer::insertFrom));
    cursor.insertHtml(newContent);
    auto table = cursor.currentTable();
    return document()->rootFrame()->childFrames().indexOf(table);
}

static const int titleMidProperty = QTextFormat::UserProperty + 1;
static const QString fullImagePrefix = QLatin1String("fullimage:");

int ChatTextViewer::showImage(const QPixmap &icon, const QString &name, const QUrl &resourceName, const QPixmap &pixmap)
{
    if(!document()->resource(QTextDocument::ImageResource, resourceName).isValid())
        addPixmapResource(resourceName, pixmap);
    return showImage(true, icon, name, resourceName);
}

int ChatTextViewer::showImage(bool fromSelf, const QPixmap &icon, const QString &name, const QUrl &url, qint64 timestamp)
{
    QTextCharFormat webImageCharFormat;
    webImageCharFormat.setObjectType(WebImageObject::objectType);
    webImageCharFormat.setProperty(QTextFormat::ImageName, url.toString());
    webImageCharFormat.setAnchorHref(fullImagePrefix+url.toString());
    auto cursor = appendTable(icon, name, timestamp, fromSelf ? (&ChatTextViewer::insertTo) : (&ChatTextViewer::insertFrom));
    cursor.insertText(QString(QChar::ObjectReplacementCharacter), webImageCharFormat);
    auto table = cursor.currentTable();
    return document()->rootFrame()->childFrames().indexOf(table);
}

bool ChatTextViewer::initRowState(int row, const QString &text, const QByteArray &mid)
{
    if(row < 0)
        return false;
    auto frames = document()->rootFrame()->childFrames();
    if(frames.size() <= row)
        return false;
    if (auto *table = qobject_cast<QTextTable *>(frames.at(row)))
    {
        auto format = table->format();
        if(!mid.isEmpty())
        {
            auto getMid = format.property(titleMidProperty).toByteArray();
            if(!getMid.isEmpty())
                return false;
            format.setProperty(titleMidProperty, mid);
            table->setFormat(format);
        }
        if(table->rows() == 3)
        {
            //3行的是To，否则没有状态格
            auto cell = table->cellAt(1, 0);

            auto cursor = cell.firstCursorPosition();
            cursor.setPosition(cell.lastPosition(), QTextCursor::KeepAnchor);
            if(cursor.hasSelection())
                cursor.removeSelectedText();
            cursor.insertText(text);
            return true;
        }
    }
    return false;
}

void ChatTextViewer::initMid(const QByteArray &mid, int row, const QString &text)
{
    if(!mid.isEmpty() && initRowState(row, text, mid)) {
        midMap[mid] = row;
    }
}

void ChatTextViewer::setMidState(const QByteArray &mid, const QString &text)
{
    auto it = midMap.constFind(mid);
    if(!mid.isEmpty() && it != midMap.constEnd()) {
        auto frames = document()->rootFrame()->childFrames();
        if(frames.size() <= it.value())
            return;
        if (auto *table = qobject_cast<QTextTable *>(frames.at(it.value())))
        {
            auto format = table->format();
            auto getMid = format.property(titleMidProperty).toByteArray();
            if(getMid != mid)
                return;

            if(table->rows() == 3)
            {
                //3行的是To，否则没有状态格
                auto cell = table->cellAt(1, 0);

                auto cursor = cell.firstCursorPosition();
                cursor.setPosition(cell.lastPosition(), QTextCursor::KeepAnchor);
                if(cursor.hasSelection())
                    cursor.removeSelectedText();
                cursor.insertText(text);
            }
        }
    }
}

void ChatTextViewer::clearAll()
{
    waitingList.clear();
    midMap.clear();
    clear();
}

void ChatTextViewer::contextMenuEvent(QContextMenuEvent *e)
{
    if(!hasFocus())
        return;
    QMenu *menu = createStandardContextMenu(e->pos());
    if (!menu)
        return;
    menu->addAction(QObject::trECSUTF8("清空"), this, SLOT(clearAll()));
    menu->setAttribute(Qt::WA_DeleteOnClose);
    menu->popup(e->globalPos());
}

void ChatTextViewer::downloadRequested(QString url)
{
    if(waitingList.contains(url))
        return;
    waitingList.append(url);
    auto get = new HttpGet(url, false, url);
    connect(get, SIGNAL(finished(QByteArray,QVariant)), SLOT(downloadFinished(QByteArray,QVariant)));
    connect(get, SIGNAL(error(QVariant)), SLOT(downloadError(QVariant)));
    connect(get, &HttpGet::downloadProgress, this, &ChatTextViewer::downloadProgress);
}


void ChatTextViewer::downloadFinished(QByteArray buffer, QVariant userData)
{
    QString name = userData.toString();
    if(name.isEmpty())
        return;
    if(!waitingList.removeOne(name))
        return;
    const char * format = Q_NULLPTR;
    QByteArray suffix = QUrl(name).fileName().section('.', -1).toLatin1().toLower();
    if(!suffix.isEmpty() && QImageReader::supportedImageFormats().contains(suffix))
        format = suffix;

    QPixmap pixmap;
    pixmap.loadFromData(buffer, format);
    if(pixmap.isNull())
        pixmap.loadFromData(buffer);
    if(pixmap.isNull())
    {
        pixmap.load(WebImageObject::brokenImage);
        document()->addResource(QTextDocument::ImageResource, QUrl(name), pixmap);
    }
    else
        addPixmapResource(QUrl(name), pixmap);

    setLineWrapColumnOrWidth(lineWrapColumnOrWidth()); // 刷新显示
}

void ChatTextViewer::downloadError(QVariant userData)
{
    QString name = userData.toString();
    if(!name.isEmpty())
        waitingList.removeOne(name);

    document()->addResource(QTextDocument::ImageResource, QUrl(name), QPixmap(WebImageObject::brokenImage));

    setLineWrapColumnOrWidth(lineWrapColumnOrWidth()); // 刷新显示
}

static const QString progressImage = QLatin1String(":/gui/picture_loading_%1.png");

void ChatTextViewer::downloadProgress(qint64 received, qint64 total, QVariant userData)
{
    if(received <= total)
    {
        QString name = userData.toString();
        int num = received * 100 / total / 10;

        document()->addResource(QTextDocument::ImageResource, QUrl(name), QPixmap(progressImage.arg(num)));

        setLineWrapColumnOrWidth(lineWrapColumnOrWidth()); // 刷新显示
    }
}

void ChatTextViewer::whenAnchorClicked(const QUrl &link)
{
    auto linkName = link.toString();
    if(!linkName.startsWith(fullImagePrefix))
        return;
    auto var = document()->resource(QTextDocument::ImageResource, link);
    if(!var.isValid())
        return;
    if(var.type() == QVariant::Image)
    {
        ImageViewer viewer;
        viewer.setImage(var.value<QImage>());
        viewer.exec();
    }
    else if (var.type() == QVariant::Pixmap)
    {
        ImageViewer viewer;
        viewer.setPixmap(var.value<QPixmap>());
        viewer.exec();
    }
}

static const QSize thumbSize(60, 44);

void ChatTextViewer::addPixmapResource(const QUrl &resourceName, const QPixmap &pixmap)
{
    auto scaled = pixmap.scaled(thumbSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
    QUrl fullResourceName = fullImagePrefix + resourceName.toString();
    document()->addResource(QTextDocument::ImageResource, resourceName, scaled);
    document()->addResource(QTextDocument::ImageResource, fullResourceName, pixmap);
}

void ChatTextViewer::addImageResource(const QUrl &resourceName, const QImage &image)
{
    auto scaled = image.scaled(thumbSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
    QUrl fullResourceName = fullImagePrefix + resourceName.toString();
    document()->addResource(QTextDocument::ImageResource, resourceName, scaled);
    document()->addResource(QTextDocument::ImageResource, fullResourceName, image);
}

QTextCursor ChatTextViewer::appendTable(const QPixmap &icon, const QString &name, qint64 timestamp, QTextCursor (ChatTextViewer::*insert)(QTextCursor &, const QPixmap &, const QString &))
{
    QTextCursor tmp(document());
    tmp.beginEditBlock();
    tmp.movePosition(QTextCursor::End);

    auto cursor = textCursor();
    auto oldBlockFormat = cursor.blockFormat();
//    if (!document()->isEmpty())
//        tmp.insertBlock(cursor.blockFormat(), cursor.charFormat());
//    else
//        tmp.setCharFormat(cursor.charFormat());

    // preserve the char format
    auto oldCharFormat = cursor.charFormat();

    QDateTime dt = (timestamp == 0) ? QDateTime::currentDateTime(): QDateTime::fromMSecsSinceEpoch(timestamp);
    timestamp = dt.toMSecsSinceEpoch();
    if(timestamp >= lastTimeStamp && (timestamp - lastTimeStamp) < 60 * 1000)
    {
        auto newBlockFormat = oldBlockFormat;
        newBlockFormat.setAlignment(Qt::AlignCenter);
        auto newChatFormat = oldCharFormat;
        newChatFormat.setBackground(Qt::gray);
        newChatFormat.setForeground(Qt::white);
        newChatFormat.setFontPointSize(10);
        tmp.insertBlock(newBlockFormat, newChatFormat);

        //append time
        auto daysTo = dt.date().daysTo(QDate::currentDate());
        auto timeString = dt.toString((daysTo == 0) ? QLatin1String("hh:mm") : QLatin1String("yyyy/M/dd hh:mm"));
        tmp.insertText(timeString);
    }

    lastTimeStamp = timestamp;

    tmp.setCharFormat(oldCharFormat);

    //do insert
    auto contentCursor = (this->*insert)(tmp, icon, name);

    if (!cursor.hasSelection())
        cursor.setCharFormat(oldCharFormat);

    tmp.endEditBlock();

    return contentCursor;
}

static void insertPixmap(QTextCursor cursor, const QPixmap &image, const QString &name = QString())
{
    if(image.isNull())
        return;
    QString imageName = name;
    if (name.isEmpty())
        imageName = QString::number(image.cacheKey());
    if(!cursor.document()->resource(QTextDocument::ImageResource, QUrl(imageName)).isValid())
        cursor.document()->addResource(QTextDocument::ImageResource, QUrl(imageName), image);
    QTextImageFormat format;
    format.setName(imageName);
    cursor.insertImage(format);
}

QTextCursor ChatTextViewer::insertTo(QTextCursor &cursor, const QPixmap &icon, const QString &)
{
    QTextTableFormat format;
    format.setBorder(0);
    format.setCellPadding(0);
    format.setCellSpacing(0);
    format.setAlignment(Qt::AlignRight);
    format.setTopMargin(12);
    format.setBottomMargin(12);
    QVector<QTextLength> constraints;
    //状态
    constraints.append(QTextLength());
    //左边
    constraints.append(QTextLength(QTextLength::FixedLength, 20));
    //中间
    constraints.append(QTextLength());
    //右边
    constraints.append(QTextLength(QTextLength::FixedLength, 33));
    //头像
    constraints.append(QTextLength(QTextLength::FixedLength, 24));
    format.setColumnWidthConstraints(constraints);
    auto table = cursor.insertTable(3, 5, format);

    auto cell = table->cellAt(0, 1);
    QTextImageFormat imageFormat;
    imageFormat.setName(QString::number(chatto_bg.topLeft.cacheKey()));
    cell.firstCursorPosition().insertImage(imageFormat);

    cell = table->cellAt(0, 2);
    auto cf = cell.format();
    cf.setBackground(chatto_bg.topCenter);
    cell.setFormat(cf);

    cell = table->cellAt(0, 3);
    imageFormat.setName(QString::number(chatto_bg.topRight.cacheKey()));
    cell.firstCursorPosition().insertImage(imageFormat);

    cell = table->cellAt(1, 3);
    cf = cell.format();
    imageFormat.setName(QString::number(chatto_bg.arrow.cacheKey()));
    cell.firstCursorPosition().insertImage(imageFormat);
    cf.setVerticalAlignment(QTextCharFormat::AlignTop);
    cf.setBackground(chatto_bg.rightCenter);
    cell.setFormat(cf);

    cell = table->cellAt(2, 3);
    imageFormat.setName(QString::number(chatto_bg.bottomRight.cacheKey()));
    cell.firstCursorPosition().insertImage(imageFormat);

    cell = table->cellAt(2, 2);
    cf = cell.format();
    cf.setBackground(chatto_bg.bottomCenter);
    cell.setFormat(cf);

    cell = table->cellAt(2, 1);
    imageFormat.setName(QString::number(chatto_bg.bottomLeft.cacheKey()));
    cell.firstCursorPosition().insertImage(imageFormat);

    cell = table->cellAt(1, 1);
    cf = cell.format();
    cf.setBackground(chatto_bg.leftCenter);
    cell.setFormat(cf);

    //icon
    cell = table->cellAt(1, 4);
    insertPixmap(cell.firstCursorPosition(), icon);

    //state
    cell = table->cellAt(1, 0);
    cf = cell.format();
    cf.setForeground(Qt::darkGray);
    cf.setFontPointSize(10);
    cell.setFormat(cf);

    //content
    cell = table->cellAt(1, 2);
    cf = cell.format();
    cf.setBackground(chatto_bg.center);
    cell.setFormat(cf);
    return cell.firstCursorPosition();
}

QTextCursor ChatTextViewer::insertFrom(QTextCursor &cursor, const QPixmap &icon, const QString &name)
{
    QTextTableFormat format;
    format.setBorder(0);
    format.setCellPadding(0);
    format.setCellSpacing(0);
    format.setAlignment(Qt::AlignLeft);
    format.setTopMargin(12);
    format.setBottomMargin(12);
    QVector<QTextLength> constraints;
    //头像
    constraints.append(QTextLength(QTextLength::FixedLength, 24));
    //左边
    constraints.append(QTextLength(QTextLength::FixedLength, 31));
    //右边
    constraints.append(QTextLength());
    //左边
    constraints.append(QTextLength(QTextLength::FixedLength, 20));
    //占位
    constraints.append(QTextLength());
    format.setColumnWidthConstraints(constraints);
    auto table = cursor.insertTable(4, 5, format);  //第一行是名字
    table->mergeCells(0, 1, 1, 4);

    auto cell = table->cellAt(1, 1);
    QTextImageFormat imageFormat;
    imageFormat.setName(QString::number(chatfrom_bg.topLeft.cacheKey()));
    cell.firstCursorPosition().insertImage(imageFormat);

    cell = table->cellAt(1, 2);
    auto cf = cell.format();
    cf.setBackground(chatfrom_bg.topCenter);
    cell.setFormat(cf);

    cell = table->cellAt(1, 3);
    imageFormat.setName(QString::number(chatfrom_bg.topRight.cacheKey()));
    cell.firstCursorPosition().insertImage(imageFormat);

    cell = table->cellAt(2, 3);
    cf = cell.format();
    cf.setBackground(chatfrom_bg.rightCenter);
    cell.setFormat(cf);

    cell = table->cellAt(3, 3);
    imageFormat.setName(QString::number(chatfrom_bg.bottomRight.cacheKey()));
    cell.firstCursorPosition().insertImage(imageFormat);

    cell = table->cellAt(3, 2);
    cf = cell.format();
    cf.setBackground(chatfrom_bg.bottomCenter);
    cell.setFormat(cf);

    cell = table->cellAt(3, 1);
    imageFormat.setName(QString::number(chatfrom_bg.bottomLeft.cacheKey()));
    cell.firstCursorPosition().insertImage(imageFormat);

    cell = table->cellAt(2, 1);
    cf = cell.format();
    imageFormat.setName(QString::number(chatfrom_bg.arrow.cacheKey()));
    cell.firstCursorPosition().insertImage(imageFormat);
    cf.setVerticalAlignment(QTextCharFormat::AlignTop);
    cf.setBackground(chatfrom_bg.leftCenter);
    cell.setFormat(cf);

    //name
    cell = table->cellAt(0, 1);
    cf = cell.format();
    cf.setProperty(QTextFormat::TableCellLeftPadding, qreal(16));
    cf.setForeground(Qt::gray);
    cf.setFontPointSize(10);
    cell.setFormat(cf);
    cell.firstCursorPosition().insertText(name);

    //icon
    cell = table->cellAt(2, 0);
    insertPixmap(cell.firstCursorPosition(), icon);

    //content
    cell = table->cellAt(2, 2);
    cf = cell.format();
    cf.setBackground(chatfrom_bg.center);
    cell.setFormat(cf);
    return cell.firstCursorPosition();
}

